package com.org.oracle.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.org.oracle.constant.RedisKeyConstants;
import com.org.oracle.constant.TaskListConstants;
import com.org.oracle.enums.ResponseCodeEnum;
import com.org.oracle.enums.SexEnum;
import com.org.oracle.enums.StatusEnum;
import com.org.oracle.exception.CustomException;
import com.org.oracle.mapper.UserCountMapper;
import com.org.oracle.mongodb.dto.*;
import com.org.oracle.mongodb.entity.User;
import com.org.oracle.mongodb.vo.*;
import com.org.oracle.mysql.vo.UserCountVO;
import com.org.oracle.properties.WeChatProperties;
import com.org.oracle.result.Result;
import com.org.oracle.service.*;
import com.org.oracle.sub.dto.EmailCheckDTO;
import com.org.oracle.util.JsonUtils;
import com.org.oracle.util.ParamUtils;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.org.oracle.enums.LoginTypeEnum;
import org.springframework.web.multipart.MultipartFile;


import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author: heiye
 * @date: 2024/10/09 下午9:30
 * @version: v1.0.0
 * @description:
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    // 微信登录接口地址
    private static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";

    @Resource
    private WeChatProperties weChatProperties;

    @Resource
    private MongoTemplate mongoTemplate;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource(name = "userExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private VerifyCodeService verifyCodeService;

    @Resource
    private CountService countService;

    @Resource
    private TaskListService taskListService;

    @Resource
    private UserDailyStudyTimeService userDailyStudyTimeService;

    @Resource
    private UserCommitResultsService userCommitResultsService;

    /**
     * 用户信息缓存
     */
    private static final Cache<Long, UserSimpleVO> LOCAL_CACHE = Caffeine.newBuilder()
            // 设置初始容量为 10000 个条目
            .initialCapacity(10000)
            // 设置缓存的最大容量为 10000 个条目
            .maximumSize(10000)
            // 设置缓存条目在写入的 1 小时后过期
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    /**
     * 用户注册
     *
     * @param userRegisterDTO
     */
    @Override
    public void register(UserRegisterDTO userRegisterDTO) {
        // 校验邮箱
        String email = userRegisterDTO.getEmail();
        EmailCheckDTO emailCheckDTO = new EmailCheckDTO();
        BeanUtils.copyProperties(userRegisterDTO, emailCheckDTO);
        // 调用验证码服务
        verifyCodeService.checkEmail(emailCheckDTO);

        Query query = Query.query(Criteria.where("email").is(email));
        User user = mongoTemplate.findOne(query, User.class);
        if (Objects.nonNull(user)) {
            throw new CustomException(ResponseCodeEnum.USER_ALREADY_EXISTS);
        }

        // 判断用户昵称是否符合规范
        String name = userRegisterDTO.getName();
        Preconditions.checkArgument(ParamUtils.checkDisplayName(name), ResponseCodeEnum.DISPLAY_NAME_VALID_FAIL);

        // 校验密码格式
        String password = userRegisterDTO.getPassword();
        Preconditions.checkArgument(ParamUtils.checkPassword(password), ResponseCodeEnum.PASSWORD_VALID_FAIL.getErrorMessage());

        // 获取reid全局唯一uid
        Long uid = redisTemplate.opsForValue().increment(RedisKeyConstants.USER_ID_GENERATOR);

        user = User.builder()
                .uid(String.valueOf(uid))
                .name(name)
                .password(passwordEncoder.encode(password))
                .email(email)
                .status(StatusEnum.ENABLE.getValue())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();

        mongoTemplate.insert(user);
    }

    /**
     * 登录
     *
     * @param userLoginDTO
     */
    @Override
    @SneakyThrows
    public UserVO login(UserLoginDTO userLoginDTO) {
        log.info("=====> login {}", userLoginDTO);

        User user = null;

        // 创建登录类型
        LoginTypeEnum loginTypeEnum = LoginTypeEnum.valueOf(userLoginDTO.getType());

        // 未知的登录类型
        if (Objects.isNull(loginTypeEnum)) {
            throw new CustomException(ResponseCodeEnum.LOGIN_TYPE_ERROR);
        }

        // 判断登录类型
        switch (loginTypeEnum) {
            // 密码登录
            case PASSWORD -> {
                String password = userLoginDTO.getPassword();

                Criteria criteria = new Criteria();

                // 手机号判空
                String phoneNumber = userLoginDTO.getPhoneNumber();
                if (StringUtils.isNotBlank(phoneNumber)) {
                    criteria.and("phoneNumber").is(phoneNumber);
                }

                // 邮箱判空
                String email = userLoginDTO.getEmail();
                if (StringUtils.isNotBlank(email)) {
                    criteria.and("email").is(email);
                }

                // 查找用户是否注册过
                Query query = Query.query(criteria);

                // 过滤信息
                query.fields().exclude("openid", "status", "createTime", "updateTime");
                user = mongoTemplate.findOne(query, User.class);

                // 判空
                if (Objects.isNull(user)) {
                    throw new CustomException(ResponseCodeEnum.USER_NOT_FOUND);
                }

                // 拿到明文密码
                String encodePassword = user.getPassword();

                // 匹配密码是否一致
                boolean isPasswordCorrect = passwordEncoder.matches(password, encodePassword);

                // 如果不正确，则抛出业务异常，提示用户名或者密码不正确
                if (!isPasswordCorrect) {
                    throw new CustomException(ResponseCodeEnum.NAME_OR_PASSWORD_ERROR);
                }
                break;
            }
            // 验证码登录
            case VERIFICATION_CODE -> throw new CustomException(ResponseCodeEnum.LOGIN_TYPE_ERROR);
            default -> throw new CustomException(ResponseCodeEnum.LOGIN_TYPE_ERROR);
        }

        // 登录用户UID
        String uid = user.getUid();

        // 登录
        StpUtil.login(uid);

        // 调用打卡服务获取时间
        CompletableFuture<Long> taskListResultFuture = CompletableFuture
                .supplyAsync(() -> taskListService.getDay(uid));

        // 调用用户学习时长服务获取用户学习时间
        CompletableFuture<Long> userDailyResultFuture = CompletableFuture
                .supplyAsync(() -> {
                    // 获取调用结果
                    Result totalStudyTimeInMinutesResult = userDailyStudyTimeService.getTotalStudyTimeInMinutes(uid);
                    // 返回调用是否成功
                    if (totalStudyTimeInMinutesResult.getSuccess()) {
                        return Long.valueOf(totalStudyTimeInMinutesResult.getData().toString());
                    }
                    return null;
                });

        // lambda 应该为最终变量
        User finalUser = user;
        String token = StpUtil.getTokenInfo().tokenValue;
        // 并行调用
        CompletableFuture<UserVO> resultFuture = CompletableFuture
                .allOf(taskListResultFuture, userDailyResultFuture)
                .thenApply(s -> {
                    // 获取返回结果
                    Long attendance = taskListResultFuture.join();
                    Long totalStudyTimeInMinutes = userDailyResultFuture.join();

                    return UserVO.builder()
                            .uid(uid)
                            .name(finalUser.getName())
                            .avatar(finalUser.getAvatar())
                            .sex(finalUser.getSex())
                            .introduction(finalUser.getIntroduction())
                            .phone(finalUser.getPhone())
                            .email(finalUser.getEmail())
                            .birthday(finalUser.getBirthday())
                            .collegeName(finalUser.getCollegeName())
                            .address(finalUser.getAddress())
                            .studyTime(totalStudyTimeInMinutes)
                            .attendance(attendance)
                            // 设置token
                            .token(token)
                            .build();
                });
        return resultFuture.get();
    }

    /**
     * 修改密码
     *
     * @param uid
     * @param updatePasswordDTO
     */
    @Override
    public void updatePassword(String uid, UpdatePasswordDTO updatePasswordDTO) {
        // 新密码
        String newPassword = updatePasswordDTO.getNewPassword();
        Preconditions.checkArgument(ParamUtils.checkPassword(newPassword), ResponseCodeEnum.PASSWORD_VALID_FAIL.getErrorMessage());
        // 密码加密
        String encodePassword = passwordEncoder.encode(newPassword);

        // 构建查询
        Query query = new Query(Criteria.where("uid").is(uid));

        // 修改密码
        Update update = new Update()
                .set("password", encodePassword)
                .set("update_time", LocalDateTime.now());

        mongoTemplate.updateFirst(query, update, User.class);
    }

    /**
     * 忘记密码
     *
     * @param forgetPasswordDTO
     */
    @Override
    public void forgetPassword(ForgetPasswordDTO forgetPasswordDTO) {
        String newPassword = forgetPasswordDTO.getNewPassword();
        Preconditions.checkArgument(ParamUtils.checkPassword(newPassword), ResponseCodeEnum.PASSWORD_VALID_FAIL.getErrorMessage());
        // 密码加密
        String encodePassword = passwordEncoder.encode(newPassword);

        // 构建查询
        Query query = new Query(Criteria.where("email").is(forgetPasswordDTO.getEmail()));

        // 修改密码
        Update update = new Update()
                .set("password", encodePassword)
                .set("update_time", LocalDateTime.now());

        mongoTemplate.updateFirst(query, update, User.class);
    }

    /**
     * 修改用户信息
     *
     * @param userUpdateInfoDTO
     * @return
     */
    @Override
    public void updateInfo(String uid, UserUpdateInfoDTO userUpdateInfoDTO) {
        // 标识位：是否需要更新
        boolean needUpdate = false;

        // 构建修改
        Update update = new Update();
        String avatar = userUpdateInfoDTO.getAvatar();
        // 校验
        if (StringUtils.isNotBlank(avatar)) {
            update.set("avatar", avatar);
            needUpdate = true;
        }

        // 校验用户名
        String name = userUpdateInfoDTO.getName();
        if (StringUtils.isNotBlank(name)) {
            Preconditions.checkArgument(ParamUtils.checkDisplayName(name), ResponseCodeEnum.DISPLAY_NAME_VALID_FAIL.getErrorMessage());
            update.set("name", name);
            needUpdate = true;
        }

        // 校验生日
        LocalDate birthday = userUpdateInfoDTO.getBirthday();
        if (Objects.nonNull(birthday)) {
            update.set("birthday", birthday);
            needUpdate = true;
        }

        // 校验性别
        Integer sex = userUpdateInfoDTO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
            update.set("sex", sex);
            needUpdate = true;
        }

        // 校验个人简介
        String introduction = userUpdateInfoDTO.getIntroduction();
        if (StringUtils.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.SYNOPSIS_VALID_FAIL.getErrorMessage());
            update.set("introduction", introduction);
            needUpdate = true;
        }

        if (needUpdate) {
            // 更新用户信息
            Query query = Query.query(Criteria.where("uid").is(uid));
            update.set("update_time", LocalDateTime.now());
            mongoTemplate.upsert(query, update, User.class);
        }
    }

    /**
     * 用户邮箱信息换绑
     *
     * @param uid
     * @param userUpdateEmailDTO
     */
    @Override
    public void updateEmail(String uid, UserUpdateEmailDTO userUpdateEmailDTO) {
        // 获取邮箱
        String email = userUpdateEmailDTO.getEmail();

        Query query = Query.query(Criteria.where("email").is(email));
        query.fields().include("uid");
        User user = mongoTemplate.findOne(query, User.class);
        // 用户判空
        if (Objects.nonNull(user)) {
            throw new CustomException(ResponseCodeEnum.EMAIL_HAS_ALREADY_SET);
        }

        // 构建调用验证码服务参数
        EmailCheckDTO emailCheckDTO = new EmailCheckDTO();
        BeanUtils.copyProperties(userUpdateEmailDTO, emailCheckDTO);
        verifyCodeService.checkEmail(emailCheckDTO);

        // 构建更改信息
        query = Query.query(Criteria.where("uid").is(uid));
        Update update = Update.update("email", email);
        mongoTemplate.updateFirst(query, update, User.class);
    }

    /**
     * 根据用户ID查找用户知识圈的详细信息
     * TODO 后期可能根据计数服务的情况来改变存储在缓存中的数据
     *      可能改变：代码、 UserDetailCircleVO 类中的变量
     *
     * @param uid
     * @return
     */
    @Override
    @SneakyThrows
    public UserDetailCircleVO findUserDetailById(String uid) {
        // 查询用户基本的信息
        UserSimpleVO userSimpleVO = findUserSimpleById(uid);

        // 调用打卡服务获取时间
        CompletableFuture<Long> taskListResultFuture = CompletableFuture
                .supplyAsync(() -> taskListService.getDay(uid));
        // 调用用户学习时长服务获取用户学习进度
        CompletableFuture<Float>  userCommitResultFuture = CompletableFuture
                .supplyAsync(() -> userCommitResultsService.getSubjectProgressByUid(uid));
        // 调用计算服务查询用户计数信息
        CompletableFuture<UserCountVO> countResultFuture = CompletableFuture
                .supplyAsync(() -> countService.findUserCount(uid));

        CompletableFuture<UserDetailCircleVO> resultFuture = CompletableFuture
                .allOf(taskListResultFuture, userCommitResultFuture, countResultFuture)
                .thenApply(s -> {
                    // 获取返回结果
                    Long attendance = taskListResultFuture.join();
                    Float studyProgress = userCommitResultFuture.join();
                    UserCountVO userCountVO = countResultFuture.join();

                    // 获得百分制
                    studyProgress *= 100;
                    // 保留两位小数
                    String progress = String.format("%.2f", studyProgress);

                    return UserDetailCircleVO
                            .builder()
                            .name(userSimpleVO.getName())
                            .avatar(userSimpleVO.getAvatar())
                            .introduction(userSimpleVO.getIntroduction())
                            .studyProgress(progress)
                            .likeTotal(userCountVO.getLikeTotal())
                            .brightTotal(userCountVO.getBrightTotal())
                            .attendance(attendance)
                            .build();
                });
        return resultFuture.get();
    }

    /**
     * 批量查询用户简要信息
     *
     * @param ids
     * @return
     */
    @Override
    public List<UserSimpleVO> findUserSimpleByIds(List<String> ids) {
        // 构建 Redis Key
        List<String> redisKeys = ids.stream().map(RedisKeyConstants::buildUserInfoKey).toList();

        // 先从 Redis 缓存中查, multiGet 批量查询提升性能
        List<Object> redisValues = redisTemplate.opsForValue().multiGet(redisKeys);

        // 如果缓存中不为空
        if (CollUtil.isNotEmpty(redisValues)) {
            // 过滤掉为空的数据
            redisValues = redisValues.stream().filter(Objects::nonNull).toList();
        }

        // 返参
        List<UserSimpleVO> userSimpleVOS = Lists.newArrayList();

        // 将过滤后的缓存集合，转换为 DTO 返参实体类
        if (CollUtil.isNotEmpty(redisValues)) {
            userSimpleVOS = redisValues.stream().map(value -> JsonUtils.parseObject(String.valueOf(value), UserSimpleVO.class)).collect(Collectors.toList());
        }

        // 如果被查询的用户信息，都在 Redis 缓存中, 则直接返回
        if (CollUtil.size(ids) == CollUtil.size(userSimpleVOS)) {
            return userSimpleVOS;
        }

        // 还有另外两种情况：一种是缓存里没有用户信息数据，还有一种是缓存里数据不全，需要从数据库中补充
        // 筛选出缓存里没有的用户数据，去查数据库
        List<String> userIdsNeedQuery = null;

        if (CollUtil.isNotEmpty(userSimpleVOS)) {
            // 将 findUserInfoByIdRspDTOS 集合转 Map
            Map<String, UserSimpleVO> map = userSimpleVOS.stream()
                    .collect(
                            Collectors.toMap(
                                    UserSimpleVO::getUid,
                                    value -> value
                            ));

            // 筛选出需要查 DB 的用户 ID
            userIdsNeedQuery = ids.stream()
                    .filter(id -> Objects.isNull(map.get(id)))
                    .toList();
        }
        // 缓存中一条用户信息都没查到，则提交的用户 ID 集合都需要查数据库
        else {
            userIdsNeedQuery = ids;
        }

        // 从数据库中批量查询用户信息
        Query query = Query.query(
                Criteria.where("uid").in(userIdsNeedQuery)
                        .and("status").is(StatusEnum.ENABLE.getValue())
        );
        query.fields().include("uid", "name", "avatar", "introduction");
        List<User> users = mongoTemplate.find(query, User.class);

        List<UserSimpleVO> userSimpleVO2 = null;

        // 若数据库查询的记录不为空
        if (CollUtil.isNotEmpty(users)) {
            // 转为VO
            userSimpleVO2 = users.stream().map(user -> UserSimpleVO.builder()
                    .uid(user.getUid())
                    .name(user.getName())
                    .avatar(user.getAvatar())
                    .introduction(user.getIntroduction())
                    .build()).toList();
            List<UserSimpleVO> finalUserSimpleVOS = userSimpleVO2;

            // 执行 pipeline 操作
            redisTemplate.executePipelined((RedisCallback<Void>) connection -> {
                for (UserSimpleVO userSimpleVO : finalUserSimpleVOS) {
                    String uid = userSimpleVO.getUid();

                    // 用户信息缓存 Redis Key
                    String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(uid);

                    // 转 JSON 字符串
                    String value = JsonUtils.toJsonString(userSimpleVO);

                    // 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
                    long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
                    redisTemplate.opsForValue().set(userInfoRedisKey, value, expireSeconds, TimeUnit.SECONDS);
                }
                return null;
            });
        }

        // 合并数据
        if (CollUtil.isNotEmpty(userSimpleVO2)) {
            userSimpleVOS.addAll(userSimpleVO2);
        }

        return userSimpleVOS;
    }

    /**
     * 基于用户id查找用户主页信息
     *
     * @return
     */
    @Override
    @SneakyThrows
    public UserInfoVO findUserById(String uid) {
        // 查询用户基本信息
        UserSimpleVO userSimpleVO = findUserSimpleById(uid);

        // 调用打卡服务获取时间
        CompletableFuture<Long> taskListResultFuture = CompletableFuture
                .supplyAsync(() -> taskListService.getDay(uid));
        // 调用用户学习时长服务获取用户学习时间
        CompletableFuture<Long> userDailyResultFuture = CompletableFuture
                .supplyAsync(() -> {
                    // 获取调用结果
                    Result totalStudyTimeInMinutesResult = userDailyStudyTimeService.getTotalStudyTimeInMinutes(uid);
                    // 返回调用是否成功
                    if (totalStudyTimeInMinutesResult.getSuccess()) {
                        return Long.valueOf(totalStudyTimeInMinutesResult.getData().toString());
                    }
                    return null;
                });
        // 调用计算服务查询用户计数信息
        CompletableFuture<UserCountVO> countResultFuture = CompletableFuture
                .supplyAsync(() -> countService.findUserCount(uid));

        // 并行调用
        CompletableFuture<UserInfoVO> resultFuture = CompletableFuture
                .allOf(taskListResultFuture, userDailyResultFuture, countResultFuture)
                .thenApply(s -> {
                    // 获取返回结果
                    Long attendance = taskListResultFuture.join();
                    Long totalStudyTimeInMinutes = userDailyResultFuture.join();
                    UserCountVO userCountVO = countResultFuture.join();

                    // 判空
                    if (Objects.isNull(totalStudyTimeInMinutes)) {
                        totalStudyTimeInMinutes = 0L;
                    }

                    return UserInfoVO
                            .builder()
                            .name(userSimpleVO.getName())
                            .avatar(userSimpleVO.getAvatar())
                            .introduction(userSimpleVO.getIntroduction())
                            .studyTime(totalStudyTimeInMinutes)
                            .likeTotal(userCountVO.getLikeTotal())
                            .noteTotal(userCountVO.getNoteTotal())
                            .brightTotal(userCountVO.getBrightTotal())
                            .attendance(attendance)
                            .build();
                });

        // 返回数据
        return resultFuture.get();
    }

    /**
     * 根据用户ID查询用户的基本信息
     * 调用了 redis 和  caffeine 形成 2 级缓存
     * 防止缓存雪崩，缓存穿透和缓存击穿的问题
     *
     * @param uid
     * @return
     */
    @Override
    public UserSimpleVO findUserSimpleById(String uid) {
        // 先从本地缓存中查询
        UserSimpleVO userSimpleVOCache = LOCAL_CACHE.getIfPresent(Long.valueOf(uid));
        if (Objects.nonNull(userSimpleVOCache)) {
            log.info("==> 命中了本地缓存：{}", userSimpleVOCache);
            return userSimpleVOCache;
        }

        // 用户缓存 Redis Key
        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(uid);

        // 先从 Redis 缓存中查询
        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoRedisKey);

        // 若 Redis 缓存中存在该用户信息
        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            // 将储存的 Json 字符串转化为对象，返回
            UserSimpleVO userSimpleVO = JsonUtils.parseObject(userInfoRedisValue, UserSimpleVO.class);

            // 异步线程中将用户信息存入本地缓存
            threadPoolTaskExecutor.submit(() -> {
                if (Objects.nonNull(userSimpleVO)) {
                    // 写入本地缓存
                    LOCAL_CACHE.put(Long.valueOf(uid), userSimpleVO);
                }
            });

            return userSimpleVO;
        }

        // 否则再从数据库中查询
        // 查询用户基本信息
        User user = findUserSimpleInfo(uid);

        // 判空
        if (Objects.isNull(user)) {
            threadPoolTaskExecutor.execute(() -> {
                // 防止缓存穿透，将空数据存入 Redis 缓存 (过期时间不宜设置过长)
                // 保底 1 分钟 + 随机秒数
                long expireSeconds = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(userInfoRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
            });
            throw new CustomException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        // 封装到缓存中
        UserSimpleVO userSimpleVO = UserSimpleVO
                .builder()
                .uid(user.getUid())
                .name(user.getName())
                .avatar(user.getAvatar())
                .introduction(user.getIntroduction())
                .build();

        // 异步将用户信息存入 Redis 缓存，提升响应速度
        threadPoolTaskExecutor.submit(() -> {
            // 过期时间 (保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大)
            long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
            redisTemplate.opsForValue().set(userInfoRedisKey, JsonUtils.toJsonString(userSimpleVO), expireSeconds, TimeUnit.SECONDS);
        });

        return userSimpleVO;
    }

    /**
     * 查询用户基本信息
     *
     * @param uid
     * @return
     */
    private User findUserSimpleInfo(String uid) {
        Query query = Query.query(
                Criteria.where("uid").is(uid)
                        .and("status").is(StatusEnum.ENABLE.getValue()));
        // 过滤字段
        query.fields().include("uid", "name", "avatar", "introduction");
        // 查询
        return mongoTemplate.findOne(query, User.class);
    }

//    ============================== 暂时不需要的服务 ==============================

    /**
     * 根据用户ID查找用户的可编辑信息
     *
     * @param uid
     * @return
     */
    @Override
    public UserEditInfoVO findUserEditInfoById(String uid) {
        // 构建条件
        Query query = new Query(Criteria.where("uid").is(uid));
        // 筛选字段
        query.fields().include("name", "avatar", "sex", "introduction", "email", "birthday");
        // 查询
        User user = mongoTemplate.findOne(query, User.class);

        // 判空
        if (Objects.isNull(user)) {
            throw new CustomException(ResponseCodeEnum.USER_NOT_FOUND);
        }
        // 构建反参
        UserEditInfoVO userEditInfoVO = new UserEditInfoVO();
        BeanUtils.copyProperties(user, userEditInfoVO);
        return userEditInfoVO;
    }

//    =============================== 已经弃用的服务及方法 ===============================

    /**
     * 微信登录
     *
     * @param dto
     * @return
     */
    @Override
    public String wxLogin(UserLoginDTO dto) {
        // 校验参数
        Preconditions.checkArgument(Objects.nonNull(dto), ResponseCodeEnum.LOGIN_FAIL);

        log.info("微信用户登录: {}", dto.getCode());

        // 获取openid
        String openid = getOpenid(dto.getCode());

        // 校验数据
        Preconditions.checkArgument(StringUtils.isNotBlank(openid), ResponseCodeEnum.LOGIN_FAIL);

        // 查找用户是否注册
        User user = mongoTemplate.findOne(Query.query(Criteria.where("openid").is(openid)), User.class);

        // 用户未注册过自动注册
        if (Objects.isNull(user)) {
            // 获取reid全局唯一uid
            Long uid = redisTemplate.opsForValue().increment(RedisKeyConstants.USER_ID_GENERATOR);

            // 构建用户信息
            user = User.builder()
                    .uid(String.valueOf(uid))
                    .openid(openid)
                    .build();

            // 初始化用户是否能点击打卡按钮
            redisTemplate.opsForValue().set(RedisKeyConstants.LOG_BUTTON + uid, TaskListConstants.NOT_PRESS_LOG_BUTTON);

            // 新增到数据库中
            mongoTemplate.insert(user);
        }

        // 登录用户
        StpUtil.login(user.getUid());

        // 获取token
        String token = StpUtil.getTokenInfo().getTokenValue();

        // 返回数据
        return token;
    }

    /**
     * 调用微信接口服务获取openid
     *
     * @param code
     * @return
     */
    private String getOpenid(String code) {
        //调用微信服务器接口获得openid
        Map<String, Object> map = new HashMap<>();
        map.put("appid", weChatProperties.getAppid());
        map.put("secret", weChatProperties.getSecret());
        map.put("js_code", code);
        map.put("grant_type", "authorization_code");

        // 发送请求
        String json = HttpUtil.get(WX_LOGIN, map);

        // 解析json
        JSONObject jsonObject = JSON.parseObject(json);

        return jsonObject.getString("openid");
    }
}
